﻿/* ***************************************************************************************
 * Linq To SystemSearch
 * 
 *
 * This is an example of how extend LinQ using IQueruable and IOrderedQuerable interfaces.
 * 
 * User this code as you want,  is free.
 * 
 * Develop by: Mario Rodriguez
 * email: marior666@hotmail.com
 * 
 ********************************************************************************************/


#region Namespace imports

using System.Linq;
using System.Linq.Expressions;
using System.Reflection;



#endregion

namespace System.Search.Linq
{
    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    internal class QueryParser<T>
    {
        #region Private members


        /// <summary>
        /// Expression being parsed.
        /// </summary>
        private Expression _expression;

        /// <summary>
        /// 
        /// </summary>
        System.Search.Linq.Query<T> _results = new System.Search.Linq.Query<T>();

        /// <summary>
        /// 
        /// </summary>
        private bool isParsingProjection  = false;

        #endregion

        #region Constructors

        SystemIndexDSQuery<T> parent;
        /// <summary>
        /// 
        /// </summary>
        /// <param name="expression"></param>
        /// <param name="p"></param>
        public QueryParser(Expression expression, SystemIndexDSQuery<T> p)
        {
            _expression = expression;
            parent = p;

            
        }

        #endregion

        #region Methods

        /// <summary>
        /// Parse all LambdaExpression .
        /// </summary>
        public System.Search.Linq.Query<T> Parse()
        {
            //
            // Do the recursive parsing.
            //
            Parse(_expression);
            return _results;

           
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="expression"></param>
        private void Parse(Expression expression)
        {
            MethodCallExpression mce = expression as MethodCallExpression;
            ConstantExpression ce = expression as ConstantExpression;

           

            //
            // Method call expression represents a query operator from the System.Linq.Queryable type.
            //
            if (mce != null && mce.Method.DeclaringType == typeof(Queryable))
            {
                //
                // Depth-first parsing of the expression tree.
                //
                Parse(mce.Arguments[0]);

                //
                // Check the extension method called during query creation.
                //
                bool error = false;
                
                switch (mce.Method.Name)
                {
                    case "Cast":
                        break;
               
                    case "TakeWhile" :{


                        LambdaExpression exp =((UnaryExpression)mce.Arguments[1]).Operand as LambdaExpression;
                        _results.TakeWhile = exp;
                      break;
                    }
                    case "SkipWhile":
                        {


                            LambdaExpression exp = ((UnaryExpression)mce.Arguments[1]).Operand as LambdaExpression;
                            _results.SkipWhile = exp;
                            break;
                        }
                    case "Where":
                        {
                           
                            Type expressionFunc = mce.Method.GetParameters()[1].ParameterType.GetGenericArguments()[0];
                            if (expressionFunc.GetGenericArguments().Length == 2)
                                _results.Where = ParsePredicate((LambdaExpression)((UnaryExpression)mce.Arguments[1]).Operand);
                            else
                                error = true;
                            break;
                        }
                   
                    case "OrderBy":
                    case "OrderByDescending":
                    case "ThenBy":
                    case "ThenByDescending":
                        {
                         
                            //TODO: implment the ordering ASC && DSC
                            if (mce.Method.GetParameters().Length == 2)
                           
                                _results.Order = ParseOrdering((LambdaExpression)((UnaryExpression)mce.Arguments[1]).Operand, mce.Method.Name.EndsWith("Descending", StringComparison.Ordinal));
                            else
                                error = true;
                            break;
                        }
                  
                    case "Select":
                        {
                            
                            isParsingProjection = true;
                            Type expressionFunc = mce.Method.GetParameters()[1].ParameterType.GetGenericArguments()[0];
                            if (expressionFunc.GetGenericArguments().Length == 2)
                                _results.Projection= ParseProjection((LambdaExpression)((UnaryExpression)mce.Arguments[1]).Operand);
                            else
                                error = true;
                            isParsingProjection = false;
                            break;
                        }
                   
                    case "Skip": {
                        SetStartAtResults((int)Expression.Lambda<Func<int>>(mce.Arguments[1]).Compile().DynamicInvoke());
                        break;
                    }
                    case "Take":
                        {
                            
                           
                            SetMaxResults((int)Expression.Lambda<Func<int>>(mce.Arguments[1]).Compile().DynamicInvoke());
                            break;
                        }
                   
                    case "First":
                    case "FirstOrDefault":
                        {

                            if (mce.Method.GetParameters().Length == 2)
                            {
                                Type expressionFunc = mce.Method.GetParameters()[1].ParameterType.GetGenericArguments()[0];
                                if (expressionFunc.GetGenericArguments().Length == 2)
                                    ParsePredicate((LambdaExpression)((UnaryExpression)mce.Arguments[1]).Operand);
                            }
                            SetMaxResults(1);
                            break;
                        }
                  
                    case "GroupBy":
                        {
                            if (mce.Method.GetParameters().Length == 2)
                                ParseGrouping((LambdaExpression)((UnaryExpression)mce.Arguments[1]).Operand);
                            else
                                error = true;
                            break;
                        }
                    //
                    // Not Supported Query Operator.
                    //
                    default:
                        error = true;
                        break;
                }
                if (error) { 
                   // this.UnsupportedQueryOperator(mce.Method.Name, ppS, ppE); /* PARSE ERROR */
                     //Notificar Error
                }
            }
            //
            // Query Source.
            //
            else if (ce != null)
            {
                Type t = ce.Value.GetType();
                if (t.GetGenericTypeDefinition() == typeof(SystemIndex<>))
                {
                    _results.MachineName = (string)(ce.Value).GetType().GetProperty("MachineName").GetValue(ce.Value,new object[]{});
                   
                }
            }
            else
            {
             //   this.UnsupportedQueryExpression(0, ppE); /* PARSE ERROR */
                //Notificar Error;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="p"></param>
        private void SetStartAtResults(int p)
        {
            _results.StarAtIndex = p;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="lambdaExpression"></param>
        private void ParseGrouping(LambdaExpression lambdaExpression)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="p"></param>
        private void SetMaxResults(int p)
        {
            _results.MaxResults = p;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="projection"></param>
        /// <returns></returns>
        private string ParseProjection(LambdaExpression projection)
        {
            NewExpression ne = projection.Body as NewExpression;
            MemberExpression me = projection.Body as MemberExpression;

            
            if (ne == null && me==null)
                throw new Exception(" Only New projections or MemberExpression are acepted");
    


            if (me != null)
                return ParseNode(me);
            
            if (ne != null) {
                string result ="";
                foreach (var arg in ne.Arguments) { 
                    
                    result += ParseNode(arg)+ " , ";
                }
                return result.Substring(0,result.LastIndexOf(" , "));
            }
            return "";
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="expression"></param>
        /// <param name="p"></param>
        /// <returns></returns>
        private string ParseOrdering(LambdaExpression expression, bool p)
        {
            return ParseProjection(expression);

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="predicate"></param>
        /// <returns></returns>
        private string ParsePredicate(LambdaExpression predicate)
        {
            string result="";
            if (predicate.Body is BinaryExpression) {
                BinaryExpression be =(BinaryExpression)predicate.Body;
                result = ParseBinaryExpression(be.Left, 
                                      be.Right,be.NodeType);
            
            }

            if (predicate.Body is UnaryExpression) {
                result=ParseUnaryExpression((UnaryExpression)predicate.Body);
            
            }

            if (predicate.Body is MethodCallExpression)
            {
                MethodCallExpression mce = predicate.Body as MethodCallExpression;
                result = ParseMethotCall(mce);
            }


            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="leftpredicate"></param>
        /// <param name="rightpredicate"></param>
        /// <param name="nodeType"></param>
        /// <returns></returns>
        private string ParseBinaryExpression(Expression leftpredicate, Expression rightpredicate,ExpressionType nodeType)
        
        {
            string left = ParseNode(leftpredicate);
            string right = ParseNode(rightpredicate);
            string boperator = GetOperator(nodeType);

            if (leftpredicate is BinaryExpression )
                left = " ( " + left + " ) ";

            if (rightpredicate is BinaryExpression)
                right = " ( " + right + " ) ";

            return left + boperator  + right ;

            
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="leftpredicate"></param>
        /// <returns></returns>
        private string ParseNode(Expression leftpredicate)
        {
            BinaryExpression be = leftpredicate as BinaryExpression;
            UnaryExpression ue = leftpredicate as UnaryExpression;
            MethodCallExpression mce = leftpredicate as MethodCallExpression;
            MemberExpression me = leftpredicate as MemberExpression;
            ConstantExpression ce = leftpredicate as ConstantExpression;
            NewArrayExpression nae = leftpredicate as NewArrayExpression;

            if (be != null)
            {
                return ParseBinaryExpression(be.Left, be.Right,be.NodeType);
            }
            else if (ue != null)
            {
                return ParseUnaryExpression(ue);
            }
            else if (mce != null)
            {
                return ParseMethotCall(mce);
            }
            else if (me != null)
            {
                return ParseMemberExpression(me);
            }
            else if (ce != null)
            {
                return ParseContstantExpression(ce);
            }
            else if (nae !=null){
                return nae.Expressions[0].ToString().Replace("\"","'");
            }
            // Lanzar una excepcion
            return "";
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ce"></param>
        /// <returns></returns>
        private string ParseContstantExpression(ConstantExpression ce)
        {
            if (ce.Value is string)
                return "'" + ce.Value.ToString() + "'";

            return ce.Value.ToString();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="me"></param>
        /// <returns></returns>
        private string ParseMemberExpression(MemberExpression me)
        {

            if (me.Expression is ConstantExpression)
            {
                ConstantExpression ce = me.Expression as ConstantExpression;

                return ce.Value.GetType().GetField(me.Member.Name).GetValue(ce.Value).ToString();
            }

            return "System" + "." +RecursiveParseMemberExpression(me as MemberExpression , !(me.Expression is MemberExpression)); 
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="me"></param>
        /// <param name="isBaseSystem"></param>
        /// <returns></returns>
        private string RecursiveParseMemberExpression(MemberExpression me, bool isBaseSystem)
        {
    
            if (me != null)
            {
               
                 if (me.Expression != null && me.Expression is MemberExpression)
                {
                     
                    
                    return RecursiveParseMemberExpression(me.Expression as MemberExpression,isBaseSystem) + "." + me.Member.Name;
                }
                else{
                    if (me.Member is MemberInfo && isBaseSystem)
                     {

                        
                        
                         if (((PropertyInfo)me.Member.ReflectedType.GetProperty(me.Member.Name)).PropertyType.IsSubclassOf(typeof(IBaseSystem)))
                          {
                             
                             
                             return ExpandClassAttributes( me.Member);
                          }
                         
                     }
                     if (isParsingProjection) {
                         //TODO verificar que sea una llamada a propiedad, NO a METODO, Verificar si tiene sentido en este punto
                         // Agreggo un nuevo mapeo

                        _results.AddColumnMapping();

                        _results.CurrentColumnMapping.PropertyInfo = me.Member.ReflectedType.GetProperty(me.Member.Name);
                        _results.CurrentColumnMapping.Name = me.Member.Name;
                     }

                     return me.Member.Name;
                 }
            }
                return "";
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="mi"></param>
        /// <returns></returns>
        private string ExpandClassAttributes(MemberInfo mi)
        {
            string result = "";
            foreach (PropertyInfo pi in mi.ReflectedType.GetProperty(mi.Name).PropertyType.GetProperties()) {
                if (isParsingProjection)
                {
                    _results.AddColumnMapping();
                    _results.CurrentColumnMapping.PropertyInfo = pi;
                    _results.CurrentColumnMapping.ObjectProperty = mi.ReflectedType.GetProperty(mi.Name);
                    _results.CurrentColumnMapping.Name = pi.Name;
                    _results.CurrentColumnMapping.ObjectType = pi.ReflectedType;
                }
                result += mi.Name + "." + pi.Name + ", System.";
            }
            result = result.Substring(0, result.LastIndexOf(","));
            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="mce"></param>
        /// <returns></returns>
        private string ParseMethotCall(MethodCallExpression mce)
        {
            switch (mce.Method.Name) {

                case "Contains":
                case "StartsWith":
                case "EndsWith":
                    {
                        string param1 = mce.Arguments.Count == 2 ? ParseNode(mce.Arguments[0]) : ParseNode(mce.Object);
                        string param2 = mce.Arguments.Count == 2 ? ParseNode(mce.Arguments[1]) : ParseNode(mce.Arguments[0]);

                        param2 = param2.Replace("\'", "");

                        switch (mce.Method.Name)
                        {

                            case "Contains":
                                {
                                    param2 = "%" + param2 + "%";
                                    break;
                                }
                            case "StartsWith":
                                {
                                    param2 = param2 + "%";
                                    break;
                                }
                            case "EndsWith":
                                {
                                    param2 = "%" + param2;
                                    break;
                                }
                        }

                        param2 = "\'" + param2 + "\'";

                        return param1 + " LIKE " + param2;
                    }

                    //return " CONTAINS( " + param1 + "," +param2+" ) " ;}
                }
            return mce.Method.Invoke(((ConstantExpression)mce.Object).Value, new object[] { }).ToString();// Error // ver si soporta operaciones
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ue"></param>
        /// <returns></returns>
        private string ParseUnaryExpression(UnaryExpression ue)
        {
            if (ue.Operand is MemberExpression || ue.Operand is ConstantExpression)
                return GetOperator(ue.NodeType) + ParseNode(ue.Operand) ;
            return GetOperator(ue.NodeType) + " ( " + ParseNode(ue.Operand) + " ) ";
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="expressionType"></param>
        /// <returns></returns>
        private string GetOperator(ExpressionType expressionType)
        {
            switch (expressionType)
            {
                //
                // AndAlso boolean expression (&&, AndAlso)
                // And boolean expression     (&,  And)
                // OrElse boolean expression  (||, OrElse)
                // Or boolean expression      (|,  Or)
                //
                case ExpressionType.AndAlso: 
                case ExpressionType.And: return " AND ";
                
                case ExpressionType.OrElse: 
                case ExpressionType.Or:  return " OR ";
                
                case ExpressionType.Equal:return " = ";
               
                case ExpressionType.Add :return " + ";

                case ExpressionType.Negate:
                
                case ExpressionType.Subtract: return " - ";
                
                case ExpressionType.LessThan: return " < ";
                
                case ExpressionType.GreaterThan: return " > ";
                
                case ExpressionType.LessThanOrEqual: return " <= ";
                
                case ExpressionType.GreaterThanOrEqual: return " >= ";
                
                case ExpressionType.Divide: return " / ";
                case ExpressionType.Not: return " NOT ";
                case ExpressionType.NotEqual: return " <> ";
            }
            // Lanzar Error
            return "";
        }


        #endregion

    }

 

    
}
